package detector.home;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URISyntaxException;
import java.util.HashSet;

import org.apache.http.client.ClientProtocolException;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import detector.audio.MonitorBar;
import detector.audio.ServerComm;
import detector.audio.TrainActivity;
import detector.audio.processing.AudioClip;
import detector.audio.processing.Monitor;
import detector.audio.processing.PeakHandler;
import detector.audio.processing.SoundClass;
import edu.washington.cs.sounddetector.server.api.exception.DetectorDoesNotExistException;
import edu.washington.cs.sounddetector.server.api.exception.SoundDetectorInternalErrorException;

/**
 * This is the entry activity for the application. If it is the users first
 * time, it loads up the first time screen, otherwise, it just displays the
 * sounds on the phone and lets the user add or remove them.
 * 
 * @author Kyle
 * 
 */
public class DetectorPhoneActivity extends Activity implements PeakHandler {
	// TODO: Don't run audio when sending stuff to server: make sure to release
	// the audio resource
	// TODO: Ensure that this is refreshed when the activity gets focus again
	private DetectorPhoneActivity thisActivity = this;

	private static String DATA_FILE_LOCATION = "datafile.dat";
	// Whether it should launch the first time activity regardless
	// of if there is already data
	private static boolean ALWAYS_FIRST_TIME = true;
	// constants for referring to different Intents
	private static final int TRAIN_REQUEST = 0;
	private static final int FIRST_REQUEST = 1;
	private static final int PICK_COLOR = 2;

	private static final String TAG = "DetectorPhoneActivity";

	ListView lvSounds;
	Button btnAddSound;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.manage);

		// Check if connected to Internet, display no connection screen
		// if no connection is available
		if (!isOnline()) {
			setContentView(R.layout.no_connection);
			return;
		}

		// Load the saved sound classes from file
		loadData();

		// SEt location name to null if we want to test the first time activity
		if (ALWAYS_FIRST_TIME) {
			ServerComm.setLocationName(null);
		}

		// Start the first time activity if the location hasn't been set
		if (ServerComm.getLocationName() == null) {
			Intent firstTimeIntent = new Intent(thisActivity,
					detector.home.FirstTimeActivity.class);
			startActivityForResult(firstTimeIntent, FIRST_REQUEST);
		} else {
			initialize();
		}
	}

	// Set up everything for this activity to work
	private void initialize() {

		// Get the sound classes stored on the server for this phone and
		// add them if they don't already exist
		try {
			ServerComm.updateSoundClassesFromServer();
		} catch (Exception ex) {
			// Crash and print this log message if it failed
			// TODO: This is not user-friendly at all,
			// The application should give some sort of feedback,
			// because network problems are always a possibility
			Log.e(TAG, Log.getStackTraceString(ex));
		}

		// Set up the sound list
		lvSounds = (ListView) findViewById(R.id.lstSounds);
		// We want the user to be able to click either the color rectangle OR
		// the
		// sound name, so we have to enable the items inside the list to get
		// focus
		lvSounds.setItemsCanFocus(true);

		// Update the list of sounds
		updateList();

		// Start listening for peaks, add this as the peakHandler
		Monitor.getMonitor().setPeakHandler(this);
		Monitor.getMonitor().startListening();
	}

	@Override
	// Create an options menu when the user presses the menu button
	public boolean onCreateOptionsMenu(Menu menu) {
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.sound_board_menu, menu);
		return true;
	}

	@Override
	// How to handle when the user clicks menu options
	public boolean onOptionsItemSelected(MenuItem item) {
		// Handle item selection
		if (item.getItemId() == R.id.mnuAddSound) {
			// Use the first time activity, but tell it that we just
			// want to train, so it doesn't have the user pick a location name
			// location name
			Monitor.getMonitor().stopListening();
			startActivityForResult(
					new Intent(thisActivity, TrainActivity.class),
					TRAIN_REQUEST);
		} else if (item.getItemId() == R.id.mnuChangeLocationName) {
			// Pop up a prompt to change the name of the location
			AlertDialog.Builder alert = new AlertDialog.Builder(thisActivity);

			alert.setTitle("Renam Location");
			alert.setMessage("Enter the new location name");
			final EditText input = new EditText(this);
			// Display the old location name to cue the user
			input.setText(ServerComm.getLocationName());
			alert.setView(input);
			alert.setPositiveButton("Ok",
					new DialogInterface.OnClickListener() {
						public void onClick(DialogInterface dialog, int which) {
							// Rename location
							try {
								ServerComm.setLocationName(input.getText()
										.toString());
							} catch (Exception ex) {
								Log.e(TAG, Log.getStackTraceString(ex));
							}
						}
					});

			alert.show();
		}

		// this method consumed the button press
		return true;
	}

	@Override
	public void finish() {
		Monitor.getMonitor().stopListening();
		saveData();
		super.finish();
	}

	/**
	 * Loads the data for this application from file - just the phone name. If
	 * file is not found, prompts for location name
	 */
	public void loadData() {
		// Load PhoneName
		FileInputStream fis = null;
		try {
			fis = this.openFileInput(DATA_FILE_LOCATION);
		} catch (FileNotFoundException e) {
			// First time - do nothing, firsttimeactivity will be run

		}

		ObjectInputStream ois = null;
		// If it worked, try initializing an object input string
		if (fis != null) {
			try {
				ois = new ObjectInputStream(fis);
			} catch (final Exception ex) {
				Log.e(TAG, Log.getStackTraceString(ex));
				this.runOnUiThread(new Runnable() {
					public void run() {
						// Report that an error happened using a toast
						Context context = getApplicationContext();
						CharSequence text = "Failed to read data file: "
								+ ex.getMessage();
						int duration = Toast.LENGTH_SHORT;
						Toast toast = Toast.makeText(context, text, duration);
						toast.show();

					}
				});
			}

			if (ois != null) {
				// Read the phone name from file
				try {
					ServerComm.setLocationName((String) ois.readObject());
				} catch (Exception ex) {
					Log.e(TAG, Log.getStackTraceString(ex));
					// Didn't read location name, toast and use default
					this.runOnUiThread(new Runnable() {
						public void run() {
							// Report that an error happened using a toast
							Context context = getApplicationContext();
							CharSequence text = "Failed to read location name, using default location "
									+ ServerComm.DEFAULT_LOCATION_NAME;
							int duration = Toast.LENGTH_SHORT;
							Toast toast = Toast.makeText(context, text,
									duration);
							toast.show();
						}
					});
				}
			}
		}
	}

	/**
	 * Saves the SoundClasses and location name
	 */
	public void saveData() {
		//TODO: Make sure this is called whenever the app closes, like if the user presses
		// the home button and not just the back button
		FileOutputStream fos;
		ObjectOutputStream oos;
		try {
			fos = this.openFileOutput(DATA_FILE_LOCATION, Context.MODE_PRIVATE);
			oos = new ObjectOutputStream(fos);
			oos.writeObject(ServerComm.getLocationName());
			oos.close();
			fos.close();
		} catch (Exception e) {
			this.runOnUiThread(new Runnable() {
				public void run() {
					// Report that an error happened using a toast
					Context context = getApplicationContext();
					CharSequence text = "Failed to save application data.";
					int duration = Toast.LENGTH_SHORT;
					Toast toast = Toast.makeText(context, text, duration);
					toast.show();
				}
			});
		}
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (resultCode == RESULT_OK
				&& (requestCode == TRAIN_REQUEST || requestCode == PICK_COLOR)) {
			// Update the list of sounds
			updateList();
			// Re-set this as the peakHandler and listen for peaks
			Monitor.getMonitor().stopListening();
			Monitor.getMonitor().setPeakHandler(this);
			Monitor.getMonitor().startListening();
		} else if (requestCode == FIRST_REQUEST && resultCode == RESULT_OK) {
			initialize();
		} else if (requestCode == FIRST_REQUEST
				&& resultCode == Activity.RESULT_CANCELED) {
			// We didn't finish the first activity, don't let the user continue
			finish();
		}
	}

	// Starts the train activity but with an
	// already created SoundClass
	private void addExamplesFor(SoundClass sc) {
		Monitor.getMonitor().stopListening();
		Intent trainIntent = new Intent(thisActivity, TrainActivity.class);
		trainIntent.putExtra("detector.audio.processing.SoundClass", sc);
		startActivityForResult(trainIntent, TRAIN_REQUEST);
	}

	private void updateList() {
		// Store the sounds to an array, -1 for the uknown soundclass
		//Only update if it's greater than 0
		if (SoundClass.getSounds().size() > 0) {
			final SoundClass[] sounds = new SoundClass[SoundClass.getSounds()
					.size() - 1];
			int i = 0;
			for (SoundClass sc : SoundClass.getSounds()) {
				// Don't let the user see the class representing an uknown
				// classification
				if (!sc.isUknown())
					sounds[i++] = sc;
			}

			this.runOnUiThread(new Runnable() {
				public void run() {
					// Generate list of sounds
					lvSounds.setAdapter((ListAdapter) new SoundClassArrayAdapter(
							thisActivity, R.layout.sound_list_item, sounds));
				}
			});
		}
	}

	public void peakDetected(AudioClip audio) {
		try {
			final SoundClass result = ServerComm.sendDetectedPeak(audio);
			this.runOnUiThread(new Runnable() {
				public void run() {
					// Print the result to the screen
					Context context = getApplicationContext();
					CharSequence text = "BLANK";
					if (result != null)
						text = "Classified as " + result.getName();
					int duration = Toast.LENGTH_LONG;

					Toast toast = Toast.makeText(context, text, duration);
					toast.show();
				}

			});

		} catch (final Exception ex) {
			Log.e(TAG, Log.getStackTraceString(ex));
			this.runOnUiThread(new Runnable() {

				public void run() {
					// Toast that a bad thing happened
					Context context = getApplicationContext();
					CharSequence text = "Failed to contact server: "
							+ ex.getMessage();
					int duration = Toast.LENGTH_SHORT;

					Toast toast = Toast.makeText(context, text, duration);
					toast.show();
				}
			});

		}
	}

	// Check if network is online
	public boolean isOnline() {
		ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
		NetworkInfo netInfo = cm.getActiveNetworkInfo();
		if (netInfo != null && netInfo.isConnectedOrConnecting()) {
			return true;
		}
		return false;
	}

	public void onPeakStarted() {
		// Not necessary to do anything when a peak is started
		// for now.
	}

	// Adapter for turning the array of soundclasses into views
	// that are displayed in our listView of soundclasses
	private class SoundClassArrayAdapter extends ArrayAdapter<SoundClass> {

		private SoundClass[] soundClasses;

		public SoundClassArrayAdapter(Context context, int resource,
				SoundClass[] objects) {
			super(context, resource, objects);
			soundClasses = objects;
		}

		@Override
		public View getView(final int position, View convertView,
				ViewGroup parent) {
			// Set the view's color rectangle to the color of the soundclass,
			// and set the label to the name of the soundclass

			// First, get the inflater so we can intantiate a view using our
			// sound_list_item layout
			LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			// Now inflate using our defined sound_list_item
			View rowView = inflater.inflate(R.layout.sound_list_item, parent,
					false);
			// The view that we need to change the background color for has the
			// id
			// rctSoundItemColor, and the view for the name has the id
			// lblSoundItemName,
			// so obtain those
			View rctSoundItemColor = (View) rowView
					.findViewById(R.id.rctSoundItemColor);
			TextView lblSoundItemName = (TextView) rowView
					.findViewById(R.id.lblSoundItemName);

			// Set the background color and name
			rctSoundItemColor.setBackgroundColor(soundClasses[position]
					.getColor());
			lblSoundItemName.setText(soundClasses[position].getName());

			// Let the user click on the color icon
			rctSoundItemColor.setClickable(true);
			rctSoundItemColor.setFocusable(true);
			// Set the behavior for when the user clicks on the color rectangle
			rctSoundItemColor.setOnClickListener(new OnClickListener() {
				public void onClick(View v) {
					// Start the color picker activity in a window
					// Define the activity we will use
					Intent colorPickerIntent = new Intent(thisActivity,
							detector.home.ColorPickerActivity.class);
					// Pass the SoundClass the user wants to change the
					// color for to the intent
					colorPickerIntent.putExtra(
							"detector.audio.processing.SoundClass",
							soundClasses[position]);
					// Start the intent and wait for a result
					startActivityForResult(colorPickerIntent, PICK_COLOR);
				}
			});

			// Not sure if this lets the user click on the button or the label
			// inside this view, and not
			// just the entire list element, or just lets the user click on the
			// whole view. It seems to be necessary
			rowView.setClickable(true);
			rowView.setFocusable(true);

			// Behavior to take when user clicks on the rest of the item
			rowView.setOnClickListener(new OnClickListener() {
				public void onClick(View v) {
					// Pop up a menu
					final CharSequence[] items = { "Add Examples", "Delete" };
					// Get the soundclass we are operating on
					final SoundClass sc = (SoundClass) lvSounds
							.getItemAtPosition(position);
					// Create the pop up menu
					AlertDialog.Builder builder = new AlertDialog.Builder(
							thisActivity);
					builder.setTitle("Pick an action:");
					builder.setItems(items,
							new DialogInterface.OnClickListener() {
								public void onClick(DialogInterface dialog,
										int item) {
									if (item == 0) { // Add Examples
										addExamplesFor(sc);
									} else { // Delete
										// Remove this sound from the sound
										// classes stored
										// on phone
										SoundClass.deleteSound(sc);
										// Update the list on the user interface
										updateList();
										try {
											// Remove this sound from the sound
											// classes
											// stored on server
											ServerComm.deleteSound(sc);
										} catch (Exception ex) {
											Log.e(TAG,
													Log.getStackTraceString(ex));
										}
									}
								}
							});
					// Show the alert that was just defined using the builder
					AlertDialog alert = builder.create();
					alert.show();
				}
			});
			rowView.setBackgroundResource(android.R.drawable.menuitem_background);

			return rowView;
		}

	}
}